package com.tom.mybatis.interceptor;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import com.thinkgem.jeesite.common.utils.DateUtils;

/**
 * Mybatis
 * @author tomZ
 * @date 2016年6月12日
 * @desc TODO
 */
@Intercepts({
		@Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
				RowBounds.class, ResultHandler.class }),
		@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class SQLInterceptor implements Interceptor, Serializable {

	private static final long serialVersionUID = 1L;

	private static Logger logger = LoggerFactory.getLogger(SQLInterceptor.class);
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		try {
			final MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
			Map<String, Object> sqlParam = Maps.newHashMap();
			if (logger.isInfoEnabled()) {
				printSql(mappedStatement, invocation, sqlParam);
			}
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}

		return invocation.proceed();
	}

	/**
	 * 获取sql 表名
	 * @param sql
	 * @return
	 */
	@SuppressWarnings("unused")
	private String getTableName(String sql) {
		if (StringUtils.isNotBlank(sql)) {
			sql = sql.toLowerCase();
		}
		String tabName = null;
		String regex = "(\\s*into\\s+\\w+\\s*)"; //insert
		Pattern p = Pattern.compile(regex);
		Matcher m = p.matcher(sql);
		if (m.find()) {
			tabName = m.group();
			tabName = tabName.replaceAll("into", "").trim();
		}
		regex = "(\\s*update\\s+\\w+\\s+)";//update
		p = Pattern.compile(regex);
		m = p.matcher(sql);
		if (m.find()) {
			tabName = m.group();
			tabName = tabName.replaceAll("update", "").trim();
		}
		regex = "(\\s+from\\s+\\w+\\s+)";//select delete
		p = Pattern.compile(regex);
		m = p.matcher(sql);
		if (m.find()) {
			tabName = m.group();
			tabName = tabName.replaceAll("from", "").trim();
		}
		return tabName;
	}

	/**
	 * 打印sql
	 * 
	 * @param mappedStatement
	 * @param invocation
	 * @param sqlParam  out
	 */
	private String printSql(MappedStatement mappedStatement, Invocation invocation, Map<String, Object> sqlParam) {
		try {
			Object parameter = invocation.getArgs()[1];
			BoundSql boundSql = mappedStatement.getBoundSql(parameter);
			if (!StringUtils.isBlank(boundSql.getSql())) {
				String sql = boundSql.getSql();
				Object parameterObject = boundSql.getParameterObject();
				Configuration configuration = mappedStatement.getConfiguration();
				MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
				List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
				if (parameterMappings != null) {
					Object value = null;
					String propertyName = null;
					for (ParameterMapping parameterMapping : parameterMappings) {

						if (parameterMapping.getMode() != ParameterMode.OUT) {
							propertyName = parameterMapping.getProperty();
							TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
							PropertyTokenizer prop = new PropertyTokenizer(propertyName);
							if (parameterObject == null) {
								value = null;
							} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
								value = parameterObject;
							} else if (boundSql.hasAdditionalParameter(propertyName)) {//
								value = boundSql.getAdditionalParameter(propertyName);
							} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
									&& boundSql.hasAdditionalParameter(prop.getName())) {
								value = boundSql.getAdditionalParameter(prop.getName());
								if (value != null) {
									value = configuration.newMetaObject(value)
											.getValue(propertyName.substring(prop.getName().length()));
								}
							} else {
								value = metaObject == null ? null : metaObject.getValue(propertyName);
							}
							// 设置参数
							sql = sql.replaceFirst("\\?", "'" + format(value) + "'");
							sqlParam.put(propertyName.toLowerCase(), value);
						}
					}
				}
				logger.info(sql);
				return sql;
			}
		} catch (Exception e) {
			logger.error(e.getMessage(),e);
		}
		return null;
	}

	private Object format(Object value) {
		if (value instanceof Date) {
			return DateUtils.formatDateTime((Date) value);
		}
		return value;
	}

	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {
	}
	
}
